using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Threading;
using System.Web.Profile;
using System.Text.RegularExpressions;
using System.Net.Mail;

namespace XZ.Content.Mailings
{
    public struct SubscriberInfo
    {
        public string UserName;
        public string Email;
        public string FirstName;
        public string LastName;
        public SubscriptionType SubscriptionType;

        public SubscriberInfo(string userName, string email, string firstName, string lastName, SubscriptionType subscriptionType)
        {
            this.UserName = userName;
            this.Email = email;
            this.FirstName = firstName;
            this.LastName = lastName;
            this.SubscriptionType = subscriptionType;
        }
    }

    public class Mailing : BizObject
    {
        private static MailingsSection Settings
        {
            get { return Mailings.Settings; }
        }

        private Guid _id = Guid.Empty;
        public Guid ID
        {
            get { return _id; }
            private set { _id = value; }
        }

        private DateTime _addedDate = DateTime.Now;
        public DateTime AddedDate
        {
            get { return _addedDate; }
            private set { _addedDate = value; }
        }

        private string _addedBy = "";
        public string AddedBy
        {
            get { return _addedBy; }
            private set { _addedBy = value; }
        }

        private string _subject = "";
        public string Subject
        {
            get { return _subject; }
            set { _subject = value; }
        }

        private string _plainTextBody = null;
        public string PlainTextBody
        {
            get
            {
                if (_plainTextBody == null)
                    FillBody();
                return _plainTextBody;
            }
            set { _plainTextBody = value; }
        }

        private string _htmlBody = null;
        public string HtmlBody
        {
            get
            {
                if (_htmlBody == null)
                    FillBody();
                return _htmlBody;
            }
            set { _htmlBody = value; }
        }

        public Mailing() { }

        public Mailing(Guid id, DateTime addedDate, string addedBy, string subject, string plainTextBody, string htmlBody)
        {
            this.ID = id;
            this.AddedDate = addedDate;
            this.AddedBy = addedBy;
            this.Subject = subject;
            this.PlainTextBody = plainTextBody;
            this.HtmlBody = htmlBody;
        }

        public bool Delete()
        {
            bool success = Mailings.DeleteMailing(this.ID);
            if (success)
                this.ID = Guid.Empty;
            return success;
        }

        public bool Update()
        {
            return Mailing.UpdateMailing(this.ID, this.Subject, this.PlainTextBody, this.HtmlBody);
        }

        private void FillBody()
        {
            Mailing record = Mailings.GetMailingByID(this.ID);
            this.PlainTextBody = record.PlainTextBody;
            this.HtmlBody = record.HtmlBody;
        }

        /***********************************
        * Static properties
        ************************************/
        public static ReaderWriterLock Lock = new ReaderWriterLock();

        private static bool _isSending = false;
        public static bool IsSending
        {
            get { return _isSending; }
            private set { _isSending = value; }
        }

        private static double _percentageCompleted = 0.0;
        public static double PercentageCompleted
        {
            get { return _percentageCompleted; }
            private set { _percentageCompleted = value; }
        }

        private static int _totalMails = -1;
        public static int TotalMails
        {
            get { return _totalMails; }
            set { _totalMails = value; }
        }

        private static int _sentMails = 0;
        public static int SentMails
        {
            get { return _sentMails; }
            private set { _sentMails = value; }
        }

        private static List<SubscriberInfo> _subscribers;
        public static List<SubscriberInfo> Subscribers
        {
            get { return _subscribers; }
            set { _subscribers = value; }
        }

        /***********************************
        * Static methods
        ************************************/

        /// <summary>
        /// Returns a collection with all newsletters sent before the specified date
        /// </summary>      
        public static List<Mailing> GetMailings()
        {
            return GetMailings(DateTime.Now);
        }
        public static List<Mailing> GetMailings(DateTime toDate)
        {
            List<Mailing> mailings = null;
            string key = "Mailings_Mailings_" + toDate.ToShortDateString();

            if (Settings.EnableCaching && BizObject.Cache[key] != null)
            {
                mailings = (List<Mailing>)BizObject.Cache[key];
            }
            else
            {
                mailings = Mailings.GetMailings(toDate);
                CacheData(key, mailings);
            }
            return mailings;
        }

        /// <summary>
        /// Returns a Mailing object with the specified ID
        /// </summary>
        public static Mailing GetMailingByID(Guid mailingID)
        {
            Mailing mailing = null;
            string key = "Mailings_Mailing_" + mailingID.ToString();

            if (Settings.EnableCaching && BizObject.Cache[key] != null)
            {
                mailing = (Mailing)BizObject.Cache[key];
            }
            else
            {
                mailing = Mailings.GetMailingByID(mailingID);
                CacheData(key, mailing);
            }
            return mailing;
        }

        /// <summary>
        /// Updates an existing Mailing
        /// </summary>
        public static bool UpdateMailing(Guid id, string subject, string plainTextBody, string htmlBody)
        {
            Mailing record = new Mailing(id, DateTime.Now, "", subject, plainTextBody, htmlBody);
            bool ret = Mailings.UpdateMailing(record);
            BizObject.PurgeCacheItems("Mailings_Mailing");
            return ret;
        }

        /// <summary>
        /// Deletes an existing Mailing
        /// </summary>
        public static bool DeleteMailing(Guid id)
        {
            bool ret = Mailings.DeleteMailing(id);
            //new RecordDeletedEvent("newsletter", id, null).Raise();
            BizObject.PurgeCacheItems("Mailings_Mailing");
            return ret;
        }

        /// <summary>
        /// Sends a Mailing
        /// </summary>
        public static Guid SendMailing(string subject, string plainTextBody, string htmlBody)
        {
            Lock.AcquireWriterLock(Timeout.Infinite);
            Mailing.TotalMails = -1;
            Mailing.SentMails = 0;
            Mailing.PercentageCompleted = 0.0;
            Mailing.IsSending = true;
            Lock.ReleaseWriterLock();

            // if the HTML body is an empty string, use the plain-text body converted to HTML
            if (htmlBody.Trim().Length == 0)
                htmlBody = Mailing.ConvertToHtml(plainTextBody);

            // create the record into the DB
            Mailing record = new Mailing(Guid.Empty, DateTime.Now,
               BizObject.CurrentUserName, subject, plainTextBody, htmlBody);
            Guid ret = Mailings.InsertMailing(record);
            BizObject.PurgeCacheItems("Mailings_Mailings_" + DateTime.Now.ToShortDateString());

            // send the Mailing asynchronously
            object[] parameters = new object[]{subject, plainTextBody, htmlBody,
            BizObject.CurrentUserName.ToLower() == "sampleeditor", HttpContext.Current};
            ParameterizedThreadStart pts = new ParameterizedThreadStart(SendEmails);
            Thread thread = new Thread(pts);
            thread.Name = "SendEmails";
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start(parameters);

            return ret;
        }

        /// <summary>
        /// Converts the input plain-text to HTML version, replacing carriage returns
        /// and spaces with <br /> and &nbsp;
        /// </summary>
        public static string ConvertToHtml(string content)
        {
            content = HttpUtility.HtmlEncode(content);
            content = content.Replace("  ", "&nbsp;&nbsp;").Replace(
               "\t", "&nbsp;&nbsp;&nbsp;").Replace("\n", "<br>");
            return content;
        }

        /// <summary>
        /// Sends the Mailing e-mails to all subscribers
        /// </summary>
        private static void SendEmails(object data)
        {
            object[] parameters = (object[])data;
            string subject = (string)parameters[0];
            string plainTextBody = (string)parameters[1];
            string htmlBody = (string)parameters[2];
            bool isTestCall = (bool)parameters[3];
            HttpContext context = (HttpContext)parameters[4];

            if (isTestCall)
            {
                Lock.AcquireWriterLock(Timeout.Infinite);
                Mailing.TotalMails = 1256;
                Lock.ReleaseWriterLock();

                for (int i = 1; i <= 1256; i++)
                {
                    Thread.Sleep(15);
                    Lock.AcquireWriterLock(Timeout.Infinite);
                    Mailing.SentMails = i;
                    Mailing.PercentageCompleted =
                       (double)Mailing.SentMails * 100 / (double)Mailing.TotalMails; ;
                    Lock.ReleaseWriterLock();
                }
            }
            else
            {
                Lock.AcquireWriterLock(Timeout.Infinite);
                Mailing.TotalMails = 0;
                Lock.ReleaseWriterLock();

                // check if the plain-text and the HTML bodies have personalization placeholders
                // that will need to be replaced on a per-mail basis. If not, the parsing will
                // be completely avoided later.
                bool plainTextIsPersonalized = HasPersonalizationPlaceholders(plainTextBody, false);
                bool htmlIsPersonalized = HasPersonalizationPlaceholders(htmlBody, true);

                if (_subscribers == null)
                    return;

                // send the Mailing
                SmtpClient smtpClient = new SmtpClient();
                smtpClient.Host = "smtp.webtechniek.nl";
                foreach (SubscriberInfo subscriber in _subscribers)
                {
                    MailMessage mail = new MailMessage();
                    mail.From = new MailAddress(Settings.FromEmail, Settings.FromDisplayName);
                    mail.To.Add(subscriber.Email);
                    mail.Subject = subject;
                    if (subscriber.SubscriptionType == SubscriptionType.PlainText)
                    {
                        string body = plainTextBody;
                        if (plainTextIsPersonalized)
                            body = ReplacePersonalizationPlaceholders(body, subscriber, false);
                        mail.Body = body;
                        mail.IsBodyHtml = false;
                    }
                    else
                    {
                        string body = htmlBody;
                        if (htmlIsPersonalized)
                            body = ReplacePersonalizationPlaceholders(body, subscriber, true);
                        mail.Body = body;
                        mail.IsBodyHtml = true;
                    }
                    try
                    {
                        smtpClient.Send(mail);
                    }
                    catch { }

                    Lock.AcquireWriterLock(Timeout.Infinite);
                    Mailing.SentMails += 1;
                    Mailing.PercentageCompleted =
                       (double)Mailing.SentMails * 100 / (double)Mailing.TotalMails;
                    Lock.ReleaseWriterLock();
                }
            }

            Lock.AcquireWriterLock(Timeout.Infinite);
            Mailing.IsSending = false;
            Lock.ReleaseWriterLock();
        }

        /// <summary>
        /// Returns whether the input text contains personalization placeholders
        /// </summary>
        private static bool HasPersonalizationPlaceholders(string text, bool isHtml)
        {
            if (isHtml)
            {
                if (Regex.IsMatch(text, @"&lt;%\s*username\s*%&gt;", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"&lt;%\s*email\s*%&gt;", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"&lt;%\s*firstname\s*%&gt;", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"&lt;%\s*lastname\s*%&gt;", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
            }
            else
            {
                if (Regex.IsMatch(text, @"<%\s*username\s*%>", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"<%\s*email\s*%>", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"<%\s*firstname\s*%>", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
                if (Regex.IsMatch(text, @"<%\s*lastname\s*%>", RegexOptions.IgnoreCase | RegexOptions.Compiled))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Replaces the input text's personalization placeholders
        /// </summary>
        private static string ReplacePersonalizationPlaceholders(string text, SubscriberInfo subscriber, bool isHtml)
        {
            if (isHtml)
            {
                text = Regex.Replace(text, @"&lt;%\s*username\s*%&gt;",
                 subscriber.UserName, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"&lt;%\s*email\s*%&gt;",
                   subscriber.Email, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"&lt;%\s*firstname\s*%&gt;",
                   (subscriber.FirstName.Length > 0 ? subscriber.FirstName : "reader"),
                   RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"&lt;%\s*lastname\s*%&gt;",
                   subscriber.LastName, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            }
            else
            {
                text = Regex.Replace(text, @"<%\s*username\s*%>",
                   subscriber.UserName, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"<%\s*email\s*%>",
                   subscriber.Email, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"<%\s*firstname\s*%>",
                   (subscriber.FirstName.Length > 0 ? subscriber.FirstName : "reader"),
                   RegexOptions.IgnoreCase | RegexOptions.Compiled);
                text = Regex.Replace(text, @"<%\s*lastname\s*%>",
                   subscriber.LastName, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            }
            return text;
        }

        /// <summary>
        /// Cache the input data, if caching is enabled
        /// </summary>
        private static void CacheData(string key, object data)
        {
            if (Settings.EnableCaching && data != null)
            {
                BizObject.Cache.Insert(key, data, null,
                   DateTime.Now.AddSeconds(Settings.CacheDuration), TimeSpan.Zero);
            }
        }
    }
}